package com.example.my_self_view.lockpattern

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.Toast
import java.util.Objects
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

class LockPatternView : View {
    // 是否初始化，确保只初始化一次
    private var mIsInit = false

    // 二维数组初始化，int[3][3]
    private var mPoints = Array(3) { Array(3) { Point(0, 0, 0) } }

    // 被点亮的点
    private var mPressPoints = arrayListOf<Point>()

    // 当前移动到的位置
    private var mX = 0f
    private var mY = 0f

    // 密码是否正确
    private var isCorrect = true
    private var password = "123845697"

    // 外圆的半径
    private var mDotRadius = 0

    // 密码错误，清空图案
    private val runnable = Runnable {
        cleanPointStatus()
    }
    private val mHandler = Handler(Looper.getMainLooper())

    // 画笔
    private lateinit var mLinePaint: Paint
    private lateinit var mPressedPaint: Paint
    private lateinit var mErrorPaint: Paint
    private lateinit var mNormalPaint: Paint
    private lateinit var mArrowPaint: Paint

    // 颜色
    private val mOuterPressedColor = 0xff8cbad8.toInt()
    private val mInnerPressedColor = 0xff0596f6.toInt()
    private val mOuterNormalColor = 0xffd9d9d9.toInt()
    private val mInnerNormalColor = 0xff929292.toInt()
    private val mOuterErrorColor = 0xff901032.toInt()
    private val mInnerErrorColor = 0xffea0945.toInt()

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(
        context,
        attrs,
        defStyleAttr,
        0
    )

    constructor(
        context: Context,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {
    }

    override fun onDraw(canvas: Canvas) {
        // 初始化九宫格，onDraw 回调用多次
        if (!mIsInit) {
            initDot()
            initPaint()
            mIsInit = true
        }

        // 绘制九宫格
        drawPoint(canvas)
        // 绘制线与监听
        drawLineAndArrow(canvas)
    }

    /**
     * 初始化点
     */
    private fun initDot() {
        // 九个宫格，存到集合 Paint[3][3]
        // 不断绘制的时候这几个点都有状态，而且后面肯定需要回调密码点都有下标 点肯定是一个对象
        // 计算中心位置
        var width = this.width // 手机屏幕宽度
        var height = this.height // 手机屏幕高度

        // 兼容横屏
        var offsetX = 0
        var offsetY = 0
        if (height > width) {
            offsetY = (height - width) / 2
            height = width
        } else {
            offsetX = (width - height) / 2
            width = height
        }

        val squareWidth = width / 3

        // 外圆的大小，根据宽度来、
        mDotRadius = width / 12

        // 计算和指定点的中心点位置
        mPoints[0][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth / 2, 0)
        mPoints[0][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth / 2, 1)
        mPoints[0][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth / 2, 2)
        mPoints[1][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth * 3 / 2, 3)
        mPoints[1][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 3 / 2, 4)
        mPoints[1][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 3 / 2, 5)
        mPoints[2][0] = Point(offsetX + squareWidth / 2, offsetY + squareWidth * 5 / 2, 6)
        mPoints[2][1] = Point(offsetX + squareWidth * 3 / 2, offsetY + squareWidth * 5 / 2, 7)
        mPoints[2][2] = Point(offsetX + squareWidth * 5 / 2, offsetY + squareWidth * 5 / 2, 8)

        // 初始化初始点
        mX = mPoints[0][0].centerX.toFloat()
        mY = mPoints[0][0].centerY.toFloat()
    }

    /**
     * 初始化画笔
     * 3个点状态的画笔，线的画笔，箭头的画笔
     */
    private fun initPaint() {
        // new Paint 对象，设置 paint 颜色
        // 线的画笔
        mLinePaint = Paint().apply {
            color = mInnerPressedColor
            style = Paint.Style.STROKE
            isAntiAlias = true
            strokeWidth = (mDotRadius / 9).toFloat()
        }
        // 按下的画笔
        mPressedPaint = Paint().apply {
            style = Paint.Style.STROKE
            isAntiAlias = true
            strokeWidth = (mDotRadius / 6).toFloat()
        }
        // 错误的画笔
        mErrorPaint = Paint().apply {
            style = Paint.Style.STROKE
            isAntiAlias = true
            strokeWidth = (mDotRadius / 6).toFloat()
        }
        // 默认的画笔
        mNormalPaint = Paint().apply {
            style = Paint.Style.STROKE
            isAntiAlias = true
            strokeWidth = (mDotRadius / 9).toFloat()
        }
        // 箭头的画笔
        mArrowPaint = Paint().apply {
            color = mInnerPressedColor
            style = Paint.Style.FILL
            isAntiAlias = true
        }
    }

    /**
     * 绘制九宫格显示
     */
    private fun drawPoint(canvas: Canvas) {
        // 绘圆
        for (i in 0..2) {
            for (point in mPoints[i]) {
                // 先绘制外圆
                mNormalPaint.color = mOuterNormalColor
                mPressedPaint.color = mOuterPressedColor
                mErrorPaint.color = mOuterErrorColor
                canvas.drawCircle(
                    point.centerX.toFloat(),
                    point.centerY.toFloat(),
                    mDotRadius.toFloat(),
                    when (point.getStatus()) {
                        Point.STATUS_NORMAL -> mNormalPaint
                        Point.STATUS_PRESSED -> mPressedPaint
                        else -> mErrorPaint
                    }
                )

                // 后绘制内圆
                mNormalPaint.color = mInnerNormalColor
                mPressedPaint.color = mInnerPressedColor
                mErrorPaint.color = mInnerErrorColor
                canvas.drawCircle(
                    point.centerX.toFloat(),
                    point.centerY.toFloat(),
                    mDotRadius / 6.toFloat(),
                    when (point.getStatus()) {
                        Point.STATUS_NORMAL -> mNormalPaint
                        Point.STATUS_PRESSED -> mPressedPaint
                        else -> mErrorPaint
                    }
                )
            }
        }
    }

    /**
     * 绘制线
     */
    private fun drawLineAndArrow(canvas: Canvas) {
        if (mPressPoints.size <= 0) return
        // 颜色
        mPressedPaint.color = mInnerPressedColor
        mErrorPaint.color = mInnerErrorColor
        // 绘线与三角形
        for (i in 0 until mPressPoints.size) {
            if (i >= 1) {
                // 绘线
                drawLine(canvas, mPressPoints[i - 1], mPressPoints[i])
                // 绘形
                drawArrow(canvas,mPressPoints[i - 1], mPressPoints[i])
            }
        }
        drawLine(canvas, mPressPoints.last(), mX, mY)
        invalidate()
    }

    /**
     * 圆与圆之间的连线
     * d = 根{ (x2 - x1)^2 + (y2 - y1)^2 }
     * p1 = [ x1 + r1(x2 - x1)/d , y1 + r1(y2 - y1)/d ]
     * p2 = [ x2 - r2(x2 - x1)/d , y2 - r2(y2 - y1)/d ]
     *
     * k = (y2 - y1) / (x2 - x1)  [x2 - x1, y2 - y1]
     * x = x1 - (x2 - x1) * t
     * y = y1 - (y2 - y1) * t
     * (x2 - (x2 - x1) * t1 - x1 + (x2 - x1) * t2 ) ==> (x2 - x1 - (x2 - x1) * t1 + (x2 - x1) * t2)
     * ==> (x2 - x1 + (x2 - x1)(t2 - t1)) => [(x2 -x1)(t2 - t1 + 1)]^2 ==> (x2 - x1)^2 * (t2 - t1 + 1)^2
     * ==>[(y2 - y1)(t2 - t1 + 1)]^2 ==> (y2 - y1)^2 * (t2 - t1 + 1)^2
     * ===> (t2 - t1 + 1)^2 * [(y2 - y1)^2 + (x2 - x1)^2]
     *
     * dx => x1 - x2 => x1 - (x2 - x1) * t1 - (x1 - (x2 - x1) * t2) => x1 - (x2 -x1) * t1 - x1 + (x2 -x1) * t2 => (x2 - x1)(t2 - t1)
     * 同理得，dy => y1 - y2 => (y2 - y1)(t2 - t1)
     * 设 a = (x2 - x1)、b = (y2 - y1)，则 dx = a(t2 - t1)、dy = b(t2 - t1)
     * 而 d = 根{ (x2 - x1)^2 + (y2 - y1)^2 }，则 d = 根{ dx^2 + dy^2 }
     * ==> d = 根{ a^2(t2 - t1)^2 + b^2(t2 - t1)^2 } ==> d = 根{ (t2 - t1)^2 * (a^2 + b^2) }
     * ===> d = |t2 - t1| * 根{ a^2 + b^2 } ===> d = |t2 - t1| * 根{ (x2 - x1)^2 + (y2 - y1)^2 }
     *
     * 已知一点(x1, y1)，参数方程为 {x = x1 + (x1 - x0) * t, y = y1 + (y1 - y0) * t}，求该点与同一直线上的一点且距离为r的点的坐标？
     * 由直观几何意义得，直线上任意一点到(x1, y1)的距离，都是(x1,y1)到(x0,y0)的距离的比例
     * 所以，d0 = |t| * 根{ (x1 - x0)^2 + (y1 - y0)^2 }
     * ==> d = |t| * d0
     * ==> r = |t| * d0
     * ==> |t| = r / d0 ==> t = -r / d0 或 t = r / d0
     * 将 t 待如 直线参数方程得
     * x = x1 + (x1 - x0) * r / d0, y = y1 + (y1 - y0) * r / d0
     * 或
     * x = x1 - (x1 - x0) * r / d0, y = y1 - (y1 - y0) * r / d0
     */
    private fun drawLine(canvas: Canvas, startCenter: Point, endCenter: Point) {
        val x1 = startCenter.centerX.toDouble()
        val x2 = endCenter.centerX.toDouble()
        val y1 = startCenter.centerY.toDouble()
        val y2 = endCenter.centerY.toDouble()
        val d = ((x2 - x1).pow(2) + (y2 - y1).pow(2)).pow(0.5)
        val r = mDotRadius / 6
        val p1X = x1 + r * (x2 - x1) / d
        val p1Y = y1 + r * (y2 - y1) / d
        val p2X = x2 - r * (x2 - x1) / d
        val p2Y = y2 - r * (y2 - y1) / d
        canvas.drawLine(
            p1X.toFloat(),
            p1Y.toFloat(),
            p2X.toFloat(),
            p2Y.toFloat(),
            if (startCenter.getStatus() == Point.STATUS_ERROR) mErrorPaint else mPressedPaint
        )
    }

    /**
     * 圆与指尖的连线
     */
    private fun drawLine(canvas: Canvas, startCenter: Point, endX: Float, endY: Float) {
        val x1 = startCenter.centerX.toDouble()
        val y1 = startCenter.centerY.toDouble()
        val d = ((endX - x1).pow(2) + (endY - y1).pow(2)).pow(0.5)
        val r = mDotRadius / 6
        val p1X = x1 + r * (endX - x1) / d
        val p1Y = y1 + r * (endY - y1) / d
        val p2X = endX - r * (endX - x1) / d
        val p2Y = endY - r * (endY - y1) / d
        canvas.drawLine(p1X.toFloat(), p1Y.toFloat(), p2X.toFloat(), p2Y.toFloat(), if (startCenter.getStatus() == Point.STATUS_ERROR) mErrorPaint else mPressedPaint)
    }

    private fun drawArrow(canvas: Canvas, startCenter: Point, endCenter: Point) {
        val len = mDotRadius / 6
        val x1 = startCenter.centerX.toDouble()
        val x2 = endCenter.centerX.toDouble()
        val y1 = startCenter.centerY.toDouble()
        val y2 = endCenter.centerY.toDouble()
        val d = ((x2 - x1).pow(2) + (y2 - y1).pow(2)).pow(0.5)
        val sin = if (y1 - y2 > 0) (y1 - y2) / d else (y2 - y1) / d
        val cos = if (x1 - x2 > 0) (x1 - x2) / d else (x2 - x1) / d
        val startX = if (x2 - x1 > 0) x2 - cos * (mDotRadius + mDotRadius / 2) else x2 + cos * (mDotRadius + mDotRadius / 2)
        val startY = if (y2 - y1 > 0) y2 - sin * (mDotRadius + mDotRadius / 2) else y2 + sin * (mDotRadius + mDotRadius / 2)
        val cx = if (x2 - x1 > 0) startX - len * cos else startX + len * cos
        val cy = if (y2 - y1 > 0) startY - len * sin else startY + len * sin
        val pa = ((cx - startX).pow(2) + (cy - startY).pow(2)).pow(0.5)
        val path = Path().apply {
            moveTo(startX.toFloat(), startY.toFloat())
            // k1 = a / b  垂直  k1 * k2 = -1  得到 k2 = - b / a
            // d = len / 2 , 任意两条垂直得直线的d0都相等，原本应该是 [(x - x1), (y - y1)]，现在是 [(y - y1), -(x - x1)]，但是d0是相等的，所以用前者也行
            lineTo((cx + (len * (startY - cy)) / (pa * 2)).toFloat(), (cy - (len * (startX - cx)) / (pa * 2)).toFloat())
            lineTo((cx - (len * (startY - cy)) / (pa * 2)).toFloat(), (cy + (len * (startX - cx)) / (pa * 2)).toFloat())
            close()
        }
        canvas.drawPath(path, if (startCenter.getStatus() == Point.STATUS_ERROR) mErrorPaint else mPressedPaint)
    }

    /**
     * 宫格的类
     */
    class Point(var centerX: Int, var centerY: Int, var index: Int) {
        companion object {
            val STATUS_NORMAL = 1
            val STATUS_PRESSED = 2
            val STATUS_ERROR = 3
        }

        // 当前点的转态 有三种状态
        private var status = STATUS_NORMAL

        fun setStatus(status: Int) { this.status = status }
        fun getStatus() = status
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                val x = event.x
                val y = event.y
                val point = checkPointPress(x, y)
                point?.apply {
                    point.setStatus(Point.STATUS_PRESSED)
                    if (!mPressPoints.contains(point)) {
                        mX = x
                        mY = y
                        mPressPoints.add(point)
                        invalidate()
                    }
                }
            }
            MotionEvent.ACTION_MOVE -> {
                val x = event.x
                val y = event.y
                mX = x
                mY = y
                val point = checkPointPress(x, y)
                point?.apply {
                    point.setStatus(Point.STATUS_PRESSED)
                    if (!mPressPoints.contains(point)) {
                        mPressPoints.add(point)
                        invalidate()
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
                val sb = StringBuilder()
                mPressPoints.forEach {
                    sb.append(it.index + 1)
                }
                if (sb.toString() == password) {
                    cleanPointStatus()
                    Toast.makeText(context, "密码正确", Toast.LENGTH_SHORT).show()
                } else {
                    mPressPoints.forEach {
                        it.setStatus(Point.STATUS_ERROR)
                    }
                    invalidate()
                    mHandler.postDelayed(runnable, 100)
                }
            }
        }
        return true
    }

    private fun checkPointPress(x: Float, y: Float): Point? {
        for (i in 0..2) {
            for (point in mPoints[i]) {
                if (isInCircle(x, y, point)) {
                    return point
                }
            }
        }
        return null
    }

    private fun isInCircle(x: Float, y: Float, point: Point): Boolean {
        val distanceX = (x - point.centerX.toDouble()).pow(2.0)
        val distanceY = (y - point.centerY.toDouble()).pow(2.0)
        return distanceX + distanceY <= mDotRadius.toDouble().pow(2.0)
    }

    private fun cleanPointStatus() {
        for (i in 0..2) {
            for (point in mPoints[i]) {
                point.setStatus(Point.STATUS_NORMAL)
            }
        }
        mPressPoints.clear()
        invalidate()
    }
}